// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>

#include <boost/asio/io_context.hpp>
#include <boost/functional/hash.hpp>
#include <glog/logging.h>

#include "coin2/base/request/http2/session.h"
#include "coin2/base/request/i_session_pool.h"
#include "coin2/base/request/session.h"
#include "coin2/base/time.h"

namespace coin2::base::request::http2 {

class SessionPool : public ISessionPool, public std::enable_shared_from_this<SessionPool> {
 public:
  using RequestType = coin2::base::request::Session::RequestType;
  using RequestPriorityWeight = coin2::base::request::RequestPriorityWeight;
  using CallbackType = coin2::base::request::Session::CallbackType;
  using Http2Session = coin2::base::request::http2::Session;

 public:
  explicit SessionPool(boost::asio::io_context* io_ctx);
  SessionPool(boost::asio::io_context* io_ctx, int max_sessions_per_host);
  SessionPool(
      boost::asio::io_context* io_ctx,
      boost::asio::ssl::context* ssl_ctx,
      int max_sessions_per_host);
  ~SessionPool() = default;
  SessionPool(const SessionPool&) = delete;
  SessionPool& operator=(const SessionPool&) = delete;

  // inherit from ISessionPool
  void AsyncRequest(
      const std::string& host,
      int port,
      const std::string& bind_ip,
      RequestType req,
      CallbackType callback,
      RequestPriorityWeight priority_weight) override;
  void StartTimer() override;
  void StopTimer() override { timer_enabled_ = false; }
  void SetTimeout(int64_t timeout_ns) override { timeout_ns_ = timeout_ns; }
  bool is_blocking() const override { return false; }  // http2 alway non-block
  void SetHttpProxy(const std::string& http_proxy) override {
    LOG(WARNING) << "http proxy is not supported yet for http2, ignored";
  }
  RttInfo GetRttInfo() override { return RttInfo(); };

 private:
  auto GetSession(const std::string& host, int port, const std::string& bind_ip)
      -> std::optional<Http2Session*>;
  void OnTimer(const boost::system::error_code& ec);

 private:
  boost::asio::io_context* io_ctx_;
  boost::asio::ssl::context ssl_ctx_;
  boost::asio::steady_timer timer_;
  int max_sessions_per_host_;
  std::map<std::string, int> sessions_per_host_;
  bool timer_enabled_ = false;
  int64_t timeout_ns_ = 30 * 1000'000'000LL;
  using Key = std::tuple<std::string, int, std::string>;
  std::unordered_map<Key, std::unique_ptr<Http2Session>, boost::hash<Key>> pools_;
  const int64_t timer_interval_sec_ = 1;
};

}  // namespace coin2::base::request::http2
